<?php

namespace App\Helpers;

use App\Exceptions\BadException;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\IdentifiedBy;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Symfony\Component\HttpFoundation\Response;

/**
 *  使用 composer require lcobucci/jwt:4.0 版本
 */
class Jwt
{
    private static $instance;
    private $config;
    private $key;
    private $iss;
    private $aud;
    private $jti;
    private $expTime;

    /**
     * 单例模式
     * @return self
     */
    public static function getInstance(): self
    {
        if (null === self::$instance) {
            self::$instance = new self();
            self::$instance->init();
        }
        return self::$instance;
    }

    private function __construct()
    {
        // 构造函数保持为空，初始化在 getInstance 中进行
    }

    /**
     * 初始化
     * @return void
     */
    private function init()
    {
        $this->loadConfig();
        $this->config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::base64Encoded($this->key)
        );
    }

    /**
     * 加载配置
     * @return void
     */
    private function loadConfig()
    {
        $this->key = env('JWT_SECRET_KEY');
        $this->iss = env('JWT_ISSUER');
        $this->aud = env('JWT_AUDIENCE');
        $this->jti = uniqid(); // 使用uniqid()生成唯一的令牌ID
        $this->expTime = (int)env('JWT_EXPIRY_HOURS'); // 过期时间以小时为单位
    }

    /**
     * 创建token
     * @param array $claims
     * @return mixed
     */
    public function createToken(array $claims)
    {
        $now = new \DateTimeImmutable();
        $builder = $this->config->builder()
            ->issuedBy($this->iss)
            ->permittedFor($this->aud)
            ->identifiedBy($this->jti)
            ->issuedAt($now)
            ->expiresAt($now->modify("+{$this->expTime} hours"));

        foreach ($claims as $key => $value) {
            $builder = $builder->withClaim($key, $value);
        }

        return $builder->getToken($this->config->signer(), $this->config->signingKey())->toString();
    }

    /**
     * 解析token
     * @param string $jwt
     * @return mixed
     */
    public function parseToken(string $jwt)
    {
        $claims = $this->config->parser()->parse($jwt)->claims();
        return json_decode(json_encode($claims->all()), true);
    }

    /**
     * 验证token
     * @param string $jwt
     * @return mixed
     */
    public function validateToken($jwt)
    {
        $config = $this->config;
        $token = $config->parser()->parse($jwt);
        $claims = $token->claims();
        $jti = (string)$claims->get('jti');
        $iss = (string)$claims->get('iss');
        $aud = $claims->get('aud');
        $exp = $claims->get('exp');
        $now = new \DateTimeImmutable();
        // 是否过期
        if ($exp < $now) {
            throw new BadException('Token已过期', Response::HTTP_UNAUTHORIZED);
        }
        // 验证jwt id是否匹配
        $validate_jwt_id = new IdentifiedBy($jti);
        // 验证签发人url是否正确
        $validate_issued = new IssuedBy($iss);
        // 验证客户端url是否匹配
        $validate_aud = new PermittedFor($aud[0]);
        $config->setValidationConstraints($validate_jwt_id, $validate_issued, $validate_aud);
        $constraints = $config->validationConstraints();
        // 验证
        if (!$config->validator()->validate($token, ...$constraints)) {
            throw new BadException('非法请求', Response::HTTP_FORBIDDEN);
        }
        return json_decode(json_encode($claims->all()), true);
    }
}
